/*

* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Smart-Shop开源商城系统/ Java商城-首选电商平台系统 请务必保留此注释头信息
* |     开源地址: https://gitee.com/ningbo-qishan/gruul-mall
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     可正常分享和学习源码,未经授权 不可商用！
* |     商业版中台购买/开源版商业授权 联系技术客服
* |     官网:  https://www.73app.cn/
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Copyright (c) 2016-2999 宁波启山智软科技有限公司
* |     版权所有 ,侵权必究！
* ------------------------------------------------------------------------

*/
package com.medusa.gruul.payment.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.medusa.gruul.common.core.exception.ServiceException;
import com.medusa.gruul.payment.api.constant.ReturnCodeConstant;
import com.medusa.gruul.payment.api.entity.Payment;
import com.medusa.gruul.payment.api.entity.PaymentRecord;
import com.medusa.gruul.payment.api.entity.PaymentWechat;
import com.medusa.gruul.payment.api.enums.*;
import com.medusa.gruul.payment.api.model.dto.PayRequestDto;
import com.medusa.gruul.payment.api.model.dto.PayResultDto;
import com.medusa.gruul.payment.api.model.dto.SftPayResultDto;
import com.medusa.gruul.payment.api.model.dto.WxPayResultDto;
import com.medusa.gruul.payment.api.model.param.sft.SftCallBack;
import com.medusa.gruul.payment.api.model.param.sft.SftWxPlatformPay;
import com.medusa.gruul.payment.api.util.GlobalConstant;
import com.medusa.gruul.payment.api.util.GlobalTools;
import com.medusa.gruul.payment.conf.PayProperty;
import com.medusa.gruul.payment.handler.AbstractHandler;
import com.medusa.gruul.payment.handler.WxPayMiniHandler;
import com.medusa.gruul.payment.service.*;
import com.medusa.gruul.platform.api.entity.MiniInfo;
import com.medusa.gruul.platform.api.feign.RemoteMiniInfoService;
import com.medusa.gruul.platform.api.model.dto.ShopConfigDto;
import com.medusa.gruul.platform.api.model.vo.PayInfoVo;
import lombok.extern.java.Log;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;


/**
 * @author create by zq
 * @date created in 2019/11/18
 */
@Log
@Service(value = "sftPayServiceImpl")
public class SftPayServiceImpl extends AbstractHandler implements SftPayService {

    @Autowired
    private RemoteMiniInfoService remoteMiniInfoService;

    @Autowired
    private PayProperty payProperty;

    @Autowired
    private IPaymentService paymentService;

    @Autowired
    private IPaymentWechatService paymentWechatService;

    @Autowired
    private IPaymentRecordService paymentRecordService;

    @Autowired
    private WxPayMiniHandler wxPayMiniHandler;

    @Autowired
    private IPaymentService iPaymentService;


    /**
     * sft 预支付
     *
     * @param payRequestDto
     * @return PayResultDto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayResultDto handle(PayRequestDto payRequestDto) {
        log.warning("请求参数 : " + JSONObject.toJSONString(payRequestDto));
        PayResultDto test = this.test(payRequestDto);
        /** 校验指定模式下的数据是否为空 */
        if (test != null) {
            return test;
        }

        if (StringUtils.isBlank(payRequestDto.getTenantId())) {
            throw new ServiceException("商户唯一标识不存在");
        }

        ShopConfigDto shopConfig = remoteMiniInfoService.getShopConfig(payRequestDto.getTenantId());
        if (shopConfig == null) {
            throw new ServiceException("商户配置不存在");
        }

        PayInfoVo payInfo = shopConfig.getPayInfo();
        if (payInfo == null) {
            throw new ServiceException("支付配置不存在");
        }

        MiniInfo miniInfo = shopConfig.getMiniInfo();
        if (miniInfo == null) {
            throw new ServiceException("未授权小程序");
        }

        payRequestDto.setFeeType(FeeTypeEnum.CNY.getName());
        payRequestDto.setPayChannel(PayChannelEnum.SFT.getType());

        /** 记录调用信息 */
        Payment payment = this.generatePayment(payRequestDto, payProperty.getSftWorkerId(), payProperty.getSftDatacenterId());
        paymentService.save(payment);
        /** 记录渠道信息数据 */
        innerHandleChannelData(payment, payRequestDto);
        /** 记录业务数据 */
        PaymentRecord paymentRecord = this.generateRecod(payRequestDto);
        paymentRecord.setPaymentId(payment.getId());

        // 封装SftWxPlatformPay
        SftWxPlatformPay sftWxPlatformPay = innerHandleSftWxPlatformPay(payRequestDto, payment, payInfo, miniInfo);
        String data = JSONObject.toJSONString(sftWxPlatformPay);
        log.info(String.format("SFT 加密前JSON data: %s", data));
        // 属性字段加密前序列调整
        HashMap reqMap = JSON.parseObject(data, LinkedHashMap.class, Feature.OrderedField);
        String signContent = HttpUtil.toParams(reqMap);
        signContent = signContent + "nbmds20200803dryuidrfg";
        log.info("SFT order param data json string：" + signContent);

        paymentRecord.setSendParam(String.format("SFT 业务参数加密前请求数据 : %s, sxfRequestPay 加密前数据 : %s",
                JSON.toJSONString(sftWxPlatformPay), data));
        paymentRecordService.save(paymentRecord);

        String sign = SecureUtil.md5(signContent);
        log.info(String.format("SFT 加密后sign: %s", sign));
        reqMap.put(GlobalConstant.STRING_SIGN, sign);

        String resultJson = null;
        SftPayResultDto.PayDataDto.CloudPayInfo payInfoData;
        try {
            resultJson = HttpUtil.post(payProperty.getSftJsApiPayUrl(), reqMap);
            // 处理支付数据
            payInfoData = innerHandlePayData(resultJson);
            if (null == payInfoData) {
                throw new ServiceException(String.format("获取 SFT 返回 RespData 数据为空! resultJson: %s", resultJson));
            }
        } catch (Exception e) {
            paymentRecord.setNotifyParam(JSONObject.toJSONString(e.getMessage()));
            throw new ServiceException(String.format("请求 SFT 发起预支付异常, 异常信息: %s", e));
        } finally {
            paymentRecord.setNotifyParam(resultJson);
            paymentRecordService.updateById(paymentRecord);
        }

        // 封装返回响应体
        return innerHandleResponseData(payInfoData, payment);
    }


    /**
     * sft第三方回调入口
     *
     * @param sftCallBack
     * @return str
     */
    @Override
    public String sftNotify(SftCallBack sftCallBack) {
        String sftCallBackJsonString = JSONObject.toJSONString(sftCallBack);
        log.info(String.format("sftNotify 返回参数: %s", sftCallBackJsonString));
        try {
            HashMap<String, Object> result = JSON.parseObject(sftCallBackJsonString, LinkedHashMap.class, Feature.OrderedField);
            final String respCode = "00";
            if (respCode.equals(result.get(GlobalConstant.STRING_RESP_CODE))) {
                String signResult = result.get(GlobalConstant.STRING_SIGN).toString();
                result.remove(GlobalConstant.STRING_SIGN);
                String resultStr = sftCallBack.getSignString();
                log.info("sftNotify 拼接参数" + resultStr);

                // 签名验证
                if (GlobalTools.verify(resultStr, signResult, GlobalConstant.STRING_SFT_PUBLIC_KEY, GlobalConstant.STRING_UTF_8)) {
                    log.info("验签成功");
                    SftCallBack responsePay = BeanUtil.mapToBean(result, new SftCallBack().getClass(), Boolean.TRUE);
                    Payment payment = iPaymentService.getBaseMapper().selectOne(new QueryWrapper<Payment>().eq("transaction_id", responsePay.getMerOrderId()));
                    innerHandleNotifyResponse(payment, sftCallBackJsonString);
                    return paymentService.innerHandleCallback(payment, responsePay.toString());
                } else {
                    log.info(String.format("验签失败, resultStr : %s, signResult: %s, key : %s", resultStr, result, GlobalConstant.STRING_SXF_PUBLIC_KEY));
                }
            }
        } catch (Throwable ex) {
            log.warning("sftNotify 回调异常 异常信息: " + ex.getMessage());
            return ReturnCodeConstant.FAIL;
        }
        return ReturnCodeConstant.FAIL;
    }


    /**
     * 更新三方回调信息
     *
     * @param payment
     * @param decryptData
     */
    private void innerHandleNotifyResponse(Payment payment, String decryptData) {
        PaymentRecord record = paymentRecordService.getBaseMapper().selectOne(new QueryWrapper<PaymentRecord>().eq("payment_id", payment.getId()));
        record.setNotifyParam(decryptData);
        paymentRecordService.getBaseMapper().updateById(record);
    }


    /**
     * 解析获取sft响应数据
     *
     * @param resultJson
     * @return JSONObject
     */
    private SftPayResultDto.PayDataDto.CloudPayInfo innerHandlePayData(String resultJson) {
        // sft返回数据调整属性序列
        SftPayResultDto result = JSON.parseObject(resultJson, SftPayResultDto.class);
        if (null != result.getData()) {
            SftPayResultDto.PayDataDto data = JSONObject.parseObject(result.getData(), SftPayResultDto.PayDataDto.class);
            SftPayResultDto.PayDataDto.CloudPayInfo cloudPayInfo = JSONObject.parseObject(data.getCloudPayInfo(), SftPayResultDto.PayDataDto.CloudPayInfo.class);
            return cloudPayInfo;
        }
        throw new ServiceException(String.format("SFT 返回数据异常! data: %s : %s", resultJson));
    }


    /**
     * 记录渠道信息数据
     *
     * @param payment
     * @param payRequestDto
     */
    private void innerHandleChannelData(Payment payment, PayRequestDto payRequestDto) {
        PaymentWechat paymentWechat = new PaymentWechat();
        paymentWechat.setPaymentId(payment.getId());
        paymentWechat.setTradeType(WxPayEnum.SFT_JS_API.getType());
        paymentWechat.setSubject(payRequestDto.getSubject());
        paymentWechat.setOutTradeNo(payRequestDto.getOutTradeNo());
        paymentWechat.setTenantId(payRequestDto.getTenantId());
        paymentWechat.setOpenId(payRequestDto.getOpenId());
        paymentWechatService.save(paymentWechat);
    }


    /**
     * 封装返回支付响应体
     *
     * @param payInfoData
     * @param payment
     * @return PayResultDto
     */
    private PayResultDto innerHandleResponseData(SftPayResultDto.PayDataDto.CloudPayInfo payInfoData, Payment payment) {
        PayResultDto test = new PayResultDto();
        payInfoData.toString();
        WxPayResultDto wxPayResultDto = new WxPayResultDto();
        wxPayResultDto.setAppId(payInfoData.getAppId());
        wxPayResultDto.setNonceStr(payInfoData.getNonceStr());
        wxPayResultDto.setPackageValue(payInfoData.getPackageStr());
        wxPayResultDto.setPaySign(payInfoData.getPaySign());
        wxPayResultDto.setSignType(payInfoData.getSignType());
        wxPayResultDto.setTimeStamp(payInfoData.getTimeStamp());
        wxPayResultDto.setResultCode(GlobalConstant.STRING_SUCCESS);
        wxPayResultDto.setTradeType(WxPayEnum.JSAPI_MINI.toString());
        wxPayResultDto.setTransactionId(payment.getTransactionId().toString());
        wxPayMiniHandler.transactionCloseTime(payment);
        test.setWxResult(wxPayResultDto);
        return test;
    }


    /**
     * 封装返回支付配置
     *
     * @param payRequestDto
     * @param payment
     * @param payInfo
     * @param miniInfo
     * @return SftWxPlatformPay
     */
    private SftWxPlatformPay innerHandleSftWxPlatformPay(PayRequestDto payRequestDto, Payment payment, PayInfoVo payInfo, MiniInfo miniInfo) {
        if (null == miniInfo || StringUtils.isBlank(miniInfo.getAppId())) {
            throw new ServiceException("商户不存在");
        }

        int maxLength = 32;
        if (payment.getTransactionId().toString().length() > maxLength) {
            throw new ServiceException("支付订单号不能超过32位");
        }

        SftWxPlatformPay platformPay = new SftWxPlatformPay();
        platformPay.setSubMerchantNo(payInfo.getSftSubMerchantNo());
        platformPay.setTerminalId(payInfo.getSftTerminalId());
        platformPay.setTotalAmount(payRequestDto.getTotalFee().toString());
        platformPay.setTxnTime(new SimpleDateFormat(GlobalConstant.STRING_YYYYMMDDHHMMSS).format(new Date()));
        platformPay.setAppId(miniInfo.getAppId());
        platformPay.setOpenId(payRequestDto.getOpenId());
        platformPay.setVersion(payProperty.getSftVersion());
        platformPay.setChannelId(payInfo.getSftChannelId());
        platformPay.setCharSet("utf8");
        platformPay.setOutOrderNo(payment.getTransactionId().toString());
        platformPay.setSignType("MD5");
        return platformPay;
    }

}
